#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
#include <Psapi.h>
#include <tlhelp32.h>
#include "Silverlight.Mainline.Injector.h"

HWND g_mainHwnd = 0;

public ref class SilverlightNativeWindow : System::Windows::Forms::NativeWindow
{
private:
	Int32 m_minWidth;
	Int32 m_minHeight;

public:
	[ComVisible(true)]
	SilverlightNativeWindow()
	{
		m_minWidth = 0;
		m_minHeight = 0;
	}

	[ComVisible(true)]
	void SetMinSize(Int32 x, Int32 y)
	{
		m_minWidth = x;
		m_minHeight = y;
	}

protected:
	virtual void WndProc(System::Windows::Forms::Message% m) override 
	{
		switch(m.Msg)
		{
		case WM_GETMINMAXINFO:
			{
				MINMAXINFO * info = (MINMAXINFO*)m.LParam.ToPointer();
				info->ptMinTrackSize.x = m_minWidth;
				info->ptMinTrackSize.y = m_minHeight;
			}
			break;
		default:
			break;
		}
		System::Windows::Forms::NativeWindow::WndProc(m);
	}
};

[ComVisible(true)]
/* The NativeWindow subclass could not be exposed directly, so we used a custom wrapper */
public ref class SilverlightNativeWindowWrapper
{
private:
	SilverlightNativeWindow ^ m_silverlightNativeWindow;

public:
	SilverlightNativeWindowWrapper(SilverlightNativeWindow ^nativeWindow)
	{
		m_silverlightNativeWindow = nativeWindow;
	}

	void SetMinSize(Int32 x, Int32 y)
	{
		m_silverlightNativeWindow->SetMinSize(x, y);
	}
};

/* The ClrProxy class is the class that gets returned
 * to Silverlight when ComAutomationFactory.CreateObject("ClrProxy") 
 * is called.  It's methods can be accessed via the dynamic keyword */
[ComVisible(true)]
public ref class ClrProxy
{
private:
	static SilverlightNativeWindowWrapper ^ m_SilverlightNativeWindowWrapper;

public: 
	Assembly^ GetAssembly(String ^ assemblyName)
	{
		return Assembly::LoadFrom(assemblyName);
	}

	Type^ GetDotNetType(Assembly ^ assembly, String ^name)
	{
		Type^ t = assembly->GetType(name);
		return t;
	}

	Object^ CreateInstance(Type ^t)
	{
		return Activator::CreateInstance(t);
	}

	Object^ GetPublicStaticPropertyValue(Type ^t, String ^name)
	{
		return t->GetProperty(name, BindingFlags::Public | BindingFlags::Static)->GetValue(nullptr, nullptr);
	}

	Object^ InvokeStaticMethod(Type ^t, String ^name, Object ^ arg)
	{
		array<Object^>^ argArray = gcnew array<Object^>(1);
		argArray[0] = arg;
		return  t->InvokeMember(name, BindingFlags::InvokeMethod | BindingFlags::Static | BindingFlags::Public, nullptr, nullptr, argArray);
	}

	void SetLayeredWindow()
	{
		HWND parent = GetParent(GetParent(g_mainHwnd));
		SetWindowLong(parent, GWL_EXSTYLE,
		GetWindowLong(parent, GWL_EXSTYLE) | WS_EX_LAYERED);
		SetLayeredWindowAttributes(parent, 0xFFFFFFFF, 100, LWA_COLORKEY);
	}

	SilverlightNativeWindowWrapper ^ GetMainWindow()
	{
		if(m_SilverlightNativeWindowWrapper == nullptr)
		{
			HWND parent = GetParent(GetParent(g_mainHwnd));
			SilverlightNativeWindow^ nativeWindow = gcnew SilverlightNativeWindow();
			nativeWindow->AssignHandle(IntPtr(parent));

			m_SilverlightNativeWindowWrapper = gcnew SilverlightNativeWindowWrapper(nativeWindow);
		}

		return m_SilverlightNativeWindowWrapper;
	}
};

/* Injects the Dll into the process belonging to the provided hWnd */
int __cdecl InjectDll(HWND windowHandle) 
{
	/* Get our memory location of our Dll, which happens to be this Dll */
	HINSTANCE hinstDll = LoadLibrary("Silverlight.Mainline.Injector.dll"); 
	HRESULT hr = E_FAIL;

	/* This hinstDll should not ever be null */
	if (hinstDll)
	{
		DWORD pId = 0;
		DWORD threadId = GetWindowThreadProcessId((HWND)windowHandle, &pId);

		if (pId)
		{
			/* Get the process handle for the Silverlight application */
			HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pId);
			if (hProcess)
			{
				/* Get the memory address for our MessageHookProc method (below) */
				HOOKPROC procAddress = (HOOKPROC)GetProcAddress(hinstDll, "MessageHookProc");

				/* Inject our Dll into the Silverlight process, and attach a hook to the message loop */
				g_messageHookHandle = SetWindowsHookEx(WH_CALLWNDPROC, procAddress, hinstDll, threadId);

				/* This will be null if we failed to inject */
				if (g_messageHookHandle)
				{
					/* Send our custom message, allowing us to block until injection */
					SendMessage(windowHandle, WM_CUSTOM, NULL, 0);
					
					/* Removes our hook from the remote process */
					UnhookWindowsHookEx(g_messageHookHandle);
					hr = S_OK;
				}

				CloseHandle(hProcess);
			}
		}
		FreeLibrary(hinstDll);
	}

	return hr;
}

/* Helper method from http://www.codeproject.com/KB/threads/ParentPID.aspx */
BOOL WINAPI GetParentPID(PROCESSENTRY32& procentry )
{
	OSVERSIONINFO  osver ;
	HINSTANCE      hInstLib ;
	HANDLE         hSnapShot ;
	BOOL           bContinue ;

	// ToolHelp Function Pointers.
	HANDLE (WINAPI *lpfCreateToolhelp32Snapshot)(DWORD,DWORD) ;
	BOOL (WINAPI *lpfProcess32First)(HANDLE,LPPROCESSENTRY32) ;
	BOOL (WINAPI *lpfProcess32Next)(HANDLE,LPPROCESSENTRY32) ;

	// Check to see if were running under Windows95 or
	// Windows NT.
	osver.dwOSVersionInfoSize = sizeof( osver ) ;
	if( !GetVersionEx( &osver ) )
		return FALSE ;

	if( osver.dwPlatformId != VER_PLATFORM_WIN32_NT )
		return FALSE;

	hInstLib = LoadLibraryA( "Kernel32.DLL" ) ;
	
	if( hInstLib == NULL )
		return FALSE ;

	// Get procedure addresses.
	// We are linking to these functions of Kernel32
	// explicitly, because otherwise a module using
	// this code would fail to load under Windows NT,
	// which does not have the Toolhelp32
	// functions in the Kernel 32.
	lpfCreateToolhelp32Snapshot=(HANDLE(WINAPI *)(DWORD,DWORD))GetProcAddress( hInstLib,"CreateToolhelp32Snapshot");
	lpfProcess32First=(BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))GetProcAddress( hInstLib, "Process32First" ) ;
	lpfProcess32Next=(BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))GetProcAddress( hInstLib, "Process32Next" ) ;

	if( lpfProcess32Next == NULL ||	lpfProcess32First == NULL || lpfCreateToolhelp32Snapshot == NULL )
	{
		FreeLibrary( hInstLib ) ;
		return FALSE ;
	}

	// Get a handle to a Toolhelp snapshot of the systems
	// processes.
	hSnapShot = lpfCreateToolhelp32Snapshot(
	TH32CS_SNAPPROCESS, 0 ) ;
	if( hSnapShot == INVALID_HANDLE_VALUE )
	{
		FreeLibrary( hInstLib ) ;
		return FALSE ;
	}

	// Get the first process' information.
	memset((LPVOID)&procentry,0,sizeof(PROCESSENTRY32));
	procentry.dwSize = sizeof(PROCESSENTRY32) ;
	bContinue = lpfProcess32First( hSnapShot, &procentry ) ;
	DWORD pid = 0;
	// While there are processes, keep looping.
	DWORD  crtpid= GetCurrentProcessId();
	
	while( bContinue )
	{
		if(crtpid == procentry.th32ProcessID)
			pid =  procentry.th32ParentProcessID;
						
		procentry.dwSize = sizeof(PROCESSENTRY32) ;
		bContinue = !pid && lpfProcess32Next( hSnapShot, &procentry );
	}

	// Free the library.
	FreeLibrary( hInstLib ) ;

	return pid?TRUE:FALSE ;
}

/* This method is compatible with RunDll32.exe, which Silverlight executes */
extern "C" DllExport 
int __cdecl InjectDll(HWND hwnd, HINSTANCE hinst, LPCSTR lpCmdLine, int nCmdShow)
{
	PROCESSENTRY32 processEntry;

	/* Here is a funky routine to get the PID of the process that started us
	 * which in this case, should be the Silverlight OOB process */
	if(!GetParentPID(processEntry))
		return FALSE;

	int remotePid = processEntry.th32ParentProcessID;
	
	bool found = false;

	HWND processHwnd = GetTopWindow(0);

	/* Enum over all our HWNDs, searching for an HWND that
	 * belongs to our parent PID (Silverlight OOB process) */
	while(processHwnd)
	{
		DWORD pid;
		DWORD dwThreadId = GetWindowThreadProcessId(processHwnd, &pid);

		if (pid == remotePid)
		{
			found = true;
			break;
		}

		processHwnd = GetNextWindow(processHwnd , GW_HWNDNEXT);
	}

	if(!found)
		return E_FAIL;

	return InjectDll(processHwnd);
}

/* This a hook proc for the real CLSIDFromProgID method in Ole32.dll */
int WINAPI CLSIDFromProgIDHooked(LPCOLESTR lpszProgID, LPCLSID lpclsid)
{
	/* Restore our hook to the original method */
	g_clsidFromProgIDHook.SwapOld((unsigned int*)g_clsidFromProgID);

	/* Strings are so much easier in .NET, so that's why I'm using .NET strings */
	String^ progId = gcnew String(lpszProgID);

	/* If Silverlight requests 'ClrProxy', then thats our queue to lie to it */
	if(progId == "ClrProxy")
	{
		/* Convert our System::Guid to a native _GUID */
		CLSID clsid = ToGUID(FAKE_CLSID);
	
		memcpy(lpclsid, &clsid, sizeof(CLSID));
		
		/* Restore our hook */
		g_clsidFromProgIDHook.SwapReset(g_clsidFromProgID);
		return S_OK;
	}

	/* Run the original CLSIDFromProgID method */
	HRESULT hr = g_clsidFromProgID(lpszProgID, lpclsid);

	/* Restore our hook */
	g_clsidFromProgIDHook.SwapReset(g_clsidFromProgID);

	return hr;
}

/* This a hook proc for the real CoCreateInstance method in Ole32.dll */
int WINAPI CoCreateInstanceHooked(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID FAR* ppv)
{
	HRESULT hr = S_OK;

	/* Restore our hook to the original method */
	g_coCreateInstanceHook.SwapOld((unsigned int*)g_coCreateInstance);

	/* Convert our System::Guid to a native _GUID */
	REFCLSID dummyGuid = ToGUID(FAKE_CLSID);

	/* If the requested CLSID matches our dummy
	 * then we create our own object instance to be returned */
	if(dummyGuid == rclsid)
	{
		ClrProxy ^proxy = gcnew ClrProxy();

		/* This get's a pointer to our .NET ClrProxy class
		 * which will look like IDispatch to Silverlight */
		IntPtr pProxy = Marshal::GetIUnknownForObject(proxy);
		*ppv = pProxy.ToPointer();
	}
	else
		/* Call the original method */
		hr = CoCreateInstance(rclsid, pUnkOuter, dwClsContext, riid, ppv);

	/* Restore our hook */
	g_coCreateInstanceHook.SwapReset(g_coCreateInstance);

	return hr;
}

int g_minWindowWidth = 320;
int g_minWindowHeight = 240;

/* This is our hook that happens when we inject the Dll into the Silverlight proc */
extern "C" DllExport  
int __cdecl MessageHookProc(int nCode, WPARAM wparam, LPARAM lparam) 
{
	CWPSTRUCT* msg = (CWPSTRUCT*)lparam;
	switch (nCode) 
    { 
        case HC_ACTION:
			if (msg->message == WM_CUSTOM)
			{
				/* Get the memory location of the ole32.dll */
				HINSTANCE hinstDLL = LoadLibrary("ole32.dll");
				/* Get the method memory address of CLSIDFromProgID method */
				g_clsidFromProgID = (CLSID_FROM_PROGID)GetProcAddress(hinstDLL, "CLSIDFromProgID");
				/* Hook that ish! */
				g_clsidFromProgIDHook.InstallHook(g_clsidFromProgID, CLSIDFromProgIDHooked);

				/* Rinse and repeat */
				hinstDLL = LoadLibrary("ole32.dll");
				g_coCreateInstance = (CO_CREATE_INSTANCE)GetProcAddress(hinstDLL, "CoCreateInstance");
				g_coCreateInstanceHook.InstallHook(g_coCreateInstance, CoCreateInstanceHooked);

				g_mainHwnd = msg->hwnd;
			}
			break;

		default:
			break;
	}
	
	return CallNextHookEx(g_messageHookHandle, nCode, wparam, lparam);
}
